﻿using System;
using ServiceStack.Redis;
using System.Text;
using System.Collections.Generic;
using LitJson;
using System.IO;
using System.Collections;
using CzfRow = System.Collections.Generic.Dictionary<string, string>;

namespace CzfSDK
{
	/// <summary>
	/// 以数据表的方式使用redis
	/// 如果要更换库,则抽出RedisClient类,改为接口即可
	/// </summary>
	public static class RedisTable
	{
		#region 操作数据
		/// <summary>
		/// 删除一行数据
		/// </summary>
		public static void RemoveRow(this RedisClient client, string db, string table, string rowId)
		{
			CzfRow row = GetRow (client, db, table, rowId);
			if (row != null) {
				List<string>[] tableIndexs = GetTableIndexs (client, db, table);
				for (int i = 0; i < tableIndexs.Length; i++) {
					List<string> index = tableIndexs [i];
					string tableIndex = GetTableIndex (client, db, table, index, row);
					client.SRem (tableIndex, Encoding.UTF8.GetBytes (row["id"]));
				}
				string rowKey = GetRowKey(db, table, rowId);
				client.Del (rowKey);
			}
		}

		/// <summary>
		/// 获取数据表中一行数据
		/// </summary>
		public static CzfRow GetRow(this RedisClient client, string db, string table, string rowId)
		{
			string rowKey = GetRowKey(db, table, rowId);
			return client.GetAllEntriesFromHash (rowKey);
		}

		/// <summary>
		/// 读取一个字段
		/// </summary>
		public static int GetInt(this RedisClient client, string db, string table, string rowId, string column)
		{
			return Convert.ToInt32 (GetString(client, db, table, rowId, column));
		}

		/// <summary>
		/// 读取一个字段
		/// </summary>
		public static string GetString(this RedisClient client, string db, string table, string rowId, string column)
		{
			string rowKey = GetRowKey(db, table, rowId);
			return client.GetValueFromHash(rowKey, column);
		}

		/// <summary>
		/// 根据索引获取1行数据
		/// </summary>
		/// <param name="indexKeys">索引key,例:"userid:itemid"</param>
		/// <param name="indexVals">索引value,例:"1002:3698"</param>
		public static CzfRow GetRowByIndex(this RedisClient client, string db, string table, string indexKeys, string indexVals)
		{
			List<CzfRow> list = GetRowsByIndex (client, db, table, indexKeys, indexVals);
			if (list != null && list.Count > 0)
				return list [0];
			else
				return null;
		}

		/// <summary>
		/// 根据索引获取多行数据
		/// </summary>
		/// <param name="indexKeys">索引key,例:"userid:itemid"</param>
		/// <param name="indexVals">索引value,例:"1002:3698"</param>
		public static List<CzfRow> GetRowsByIndex(this RedisClient client, string db, string table, string indexKeys, string indexVals)
		{
			string tableIndex = db + ":" + table + ":index:" + indexKeys +":"+ indexVals;
			byte[][] ids = client.SMembers (tableIndex);
			if (ids == null || ids.Length == 0)
				return null;
			
			List<CzfRow> ret = new List<CzfRow> (ids.Length);
			foreach (byte[] bytes in ids) {
				string id = Encoding.UTF8.GetString (bytes);
				CzfRow row = GetRow (client, db, table, id);
				ret.Add (row);
			}
			return ret;
		}

		/// <summary>
		/// 获取表中所有数据
		/// </summary>
		public static List<CzfRow> GetAllRows(this RedisClient client, string db, string table)
		{
			string tableIdKey = GetTableId (db, table);
			int maxId = client.Get<int> (tableIdKey);

			List<CzfRow> ret = new List<CzfRow> ();
			for (int i = 1; i <= maxId; i++) {
				CzfRow row = GetRow (client, db, table, i + "");
				if (row != null)
					ret.Add (row);
			}

			return ret;
		}

		/// <summary>
		/// 新增一行数据
		/// </summary>
		public static CzfRow CreateRow(this RedisClient client, string db, string table, CzfRow row)
		{
			string tableIdKey = GetTableId (db, table);
			string rowId = client.Incr (tableIdKey) + "";
			row["id"] = rowId;
			byte[] rowIdBytes = Encoding.UTF8.GetBytes (rowId);

			List<string>[] tableIndexs = GetTableIndexs (client, db, table);
			for (int i = 0; i < tableIndexs.Length; i++) {
				List<string> index = tableIndexs [i];
				string tableIndex = GetTableIndex (client, db, table, index, row);
				client.SAdd (tableIndex, rowIdBytes);
			}

			string rowKey = GetRowKey (db, table, rowId);
			foreach (string key in row.Keys) {
				if(row [key] != null)
					client.SetEntryInHash (rowKey, key, row [key]);
			}

			return row;
		}

		/// <summary>
		/// 更新一行数据
		/// </summary>
		public static void UpdateRow(this RedisClient client, string db, string table, CzfRow row)
		{
			string rowId = row ["id"];
			byte[] rowIdBytes = Encoding.UTF8.GetBytes (rowId);

			CzfRow oldRow = new CzfRow();
			oldRow ["id"] = rowId;

			List<string>[] tableIndexs = GetTableIndexs (client, db, table);
			for (int i = 0; i < tableIndexs.Length; i++) {
				List<string> index = tableIndexs [i];
				string tableIndexOld = GetTableIndex (client, db, table, index, oldRow);
				string tableIndexNew = GetTableIndex (client, db, table, index, row);

				client.SRem (tableIndexOld, rowIdBytes);
				client.SAdd (tableIndexNew, rowIdBytes);
			}

			string rowKey = GetRowKey (db, table, rowId);
			foreach (string key in row.Keys) {
				client.SetEntryInHash (rowKey, key, row [key]);
			}
		}

		/// <summary>
		/// 更新一个字段
		/// </summary>
		public static void SetString(this RedisClient client, string db, string table, string rowId, string column, string value)
		{
			if (IsIndex (client, db, table, column)) {
				// 需要更新索引
				byte[] rowIdBytes = Encoding.UTF8.GetBytes (rowId);
				CzfRow oldRow = new CzfRow();
				oldRow ["id"] = rowId;
				CzfRow row = new CzfRow();
				row ["id"] = rowId;
				row [column] = value;

				List<string>[] tableIndexs = GetTableIndexs (client, db, table);
				foreach (List<string> index in tableIndexs) {
					if (index.Contains (column)) {
						string tableIndexOld = GetTableIndex (client, db, table, index, oldRow);
						string tableIndexNew = GetTableIndex (client, db, table, index, row);

						client.SRem (tableIndexOld, rowIdBytes);
						client.SAdd (tableIndexNew, rowIdBytes);
					}
				}
			}

			string rowKey = GetRowKey (db, table, rowId);
			client.SetEntryInHash (rowKey, column, value);
		}
		#endregion

		#region 操作表
		/// <summary>
		/// 创建一个索引
		/// </summary>
		public static void CreateIndex(this RedisClient client, string db, string table, string index)
		{
			List<string> lIndex = new List<string> (index.Split(':'));
			string tableIndexsKey = GetTableIndexsKey(db, table);
			client.SAdd(tableIndexsKey, Encoding.UTF8.GetBytes (index));
			string tableIdKey = GetTableId (db, table);
			int maxId = client.Get<int> (tableIdKey);

			for (int i = 1; i <= maxId; i++) {
				CzfRow row = GetRow (client, db, table, i + "");
				string tableIndex = GetTableIndex (client, db, table, lIndex, row);
				client.SAdd (tableIndex, Encoding.UTF8.GetBytes (row["id"]));
			}
		}

		/// <summary>
		/// 创建表
		/// </summary>
		public static bool CreateTable(this RedisClient client, string db, string tableName, params string[] indexs)
		{
			string tablesKey = GetTablesKey(db);
			byte[] tableNameBytes = Encoding.UTF8.GetBytes (tableName);

			if (client.SIsMember(tablesKey, tableNameBytes) == 1)
				return false; // 已存在

			client.SAdd (tablesKey, tableNameBytes);
			if (indexs != null && indexs.Length > 0) {
				// 创建索引
				string tableIndexsKey = GetTableIndexsKey(db, tableName);
				foreach(string index in indexs)
					client.SAdd(tableIndexsKey, Encoding.UTF8.GetBytes (index));
			}

			return true;
		}

		/// <summary>
		/// 删除表
		/// </summary>
		public static void RemoveTable(this RedisClient client, string db, string table)
		{
			string tablesKey = GetTablesKey (db);
			string tableIndexKey = GetTableIndexsKey (db, table);
			string tableIdKey = GetTableId (db, table);
			int maxId = client.Get<int> (tableIdKey);

			for (int i = 1; i <= maxId; i++) {
				RemoveRow (client, db, table, i + "");
			}

			client.Del (tableIdKey);
			client.Del (tableIndexKey);
			client.SRem (tablesKey, Encoding.UTF8.GetBytes(table));
		}

		/// <summary>
		/// 获取db的所有数据表名
		/// </summary>
		public static List<string> GetTables(this RedisClient client, string db)
		{
			string tablesKey = GetTablesKey(db);
			byte[][] result = client.SMembers (tablesKey);
			if (result == null || result.Length == 0)
				return null;

			List<string> ret = new List<string> (result.Length);
			foreach (byte[] bytes in result) {
				ret.Add(Encoding.UTF8.GetString(bytes));
			}
			
			return ret;
		}
		#endregion

		#region 操作库
		/// <summary>
		/// 创建库
		/// </summary>
		public static bool CreateDb(this RedisClient client, string db)
		{
			string dbsKey = GetDBsKey ();
			byte[] dbBytes = Encoding.UTF8.GetBytes (db);

			if (client.SIsMember(dbsKey, dbBytes) == 1)
				return false; // 已存在

			client.SAdd (dbsKey, dbBytes);
			return true;
		}

		/// <summary>
		/// 获取所有库
		/// </summary>
		public static List<string> GetDbs(this RedisClient client)
		{
			string dbsKey = GetDBsKey ();
			byte[][] result = client.SMembers (dbsKey);
			if (result == null || result.Length == 0)
				return null;

			List<string> ret = new List<string> (result.Length);
			foreach (byte[] bytes in result) {
				ret.Add(Encoding.UTF8.GetString(bytes));
			}

			return ret;
		}
		#endregion

		#region 导入导出
		/// <summary>
		/// 导入数据
		/// </summary>
		public static void Import(this RedisClient client, JsonData json)
		{
			string db = json["db"].ToString();
			string table = json ["name"].ToString();
			string maxId = json ["maxid"].ToString ();
			IList listIndex = (IList)json["index"];
			IDictionary dictData = (IDictionary)json["data"];
			// 创建表结构
			string[] indexs = new string[listIndex.Count];
			for (int i = 0; i < listIndex.Count; i++) {
				indexs [i] = listIndex[i].ToString();
			}
			CreateTable(client, db, table, indexs);
			// 写入自增ID当前值
			string tableIdKey = GetTableId (db, table);
			client.Set<string> (tableIdKey, maxId);
			// 写入数据
			foreach (IDictionary rowData in dictData.Values) {
				CzfRow row = new CzfRow (rowData.Count);
				foreach (string key in rowData.Keys) {
					row [key] = rowData [key].ToString();
				}
				UpdateRow (client, db, table, row);
			}
		}

		/// <summary>
		/// 导出数据
		/// </summary>
		public static JsonData Export(this RedisClient client, string db, string table)
		{
			string tableIdKey = GetTableId (db, table);
			int maxId = client.Get<int> (tableIdKey);

			JsonData data = new JsonData ();
			for (int i = 1; i <= maxId; i++) {
				CzfRow row = GetRow (client, db, table, i + "");
				if (row != null) {
					JsonData rowData = new JsonData ();
					foreach (string key in row.Keys) {
						rowData [key] = row [key];
					}
					data [i + ""] = rowData;
				}
			}

			JsonData index = new JsonData ();
			index.SetJsonType (JsonType.Array);
			string tableIndexsKey = GetTableIndexsKey (db, table);
			byte[][] result = client.SMembers (tableIndexsKey);
			if (result != null && result.Length > 0) {
				foreach (byte[] bytes in result) {
					index.Add(Encoding.UTF8.GetString(bytes));
				}
			}

			JsonData ret = new JsonData ();
			ret ["db"] = db;
			ret ["name"] = table;
			ret ["maxid"] = maxId;
			ret ["index"] = index;
			ret ["data"] = data;

			return ret;
		}
		#endregion

		#region private
		private static HashSet<string> _IndexColumns = new HashSet<string>();
		/// <summary>
		/// 判断某列是不是索引（缓存）
		/// </summary>
		private static bool IsIndex(this RedisClient client, string db, string table, string column)
		{
			string tableIndexsKey = GetTableIndexsKey(db, table);
			if (!_tableIndexs.ContainsKey (tableIndexsKey))
				GetTableIndexs (client, db, table);

			return _IndexColumns.Contains(db + ":" + table + ":" + column);
		}

		private static Dictionary<string, List<string>[]> _tableIndexs = new Dictionary<string, List<string>[]>();
		/// <summary>
		/// 获取表的所有索引字段（缓存）
		/// </summary>
		private static List<string>[] GetTableIndexs(this RedisClient client, string db, string table)
		{
			string tableIndexsKey = GetTableIndexsKey(db, table);
			if(_tableIndexs.ContainsKey(tableIndexsKey))
				return _tableIndexs[tableIndexsKey];
			
			byte[][] result = client.SMembers (tableIndexsKey);
			if (result == null || result.Length == 0) {
				_tableIndexs [tableIndexsKey] = null;
				return null;
			}

			List<string>[] ret = new List<string>[result.Length];
			for (int i = 0; i < result.Length; i++) {
				byte[] bytes = result [i];
				string s = Encoding.UTF8.GetString (bytes);
				ret[i] = new List<string>(s.Split (':'));
				foreach (string column in ret[i]) {
					_IndexColumns.Add (db + ":" + table + ":" + column);
				}
			}
			_tableIndexs [tableIndexsKey] = ret;
			return ret;
		}

		private static string GetRowKey(string db, string table, string rowId)
		{
			return db + ":" + table + ":" + rowId;
		}

		private static string GetDBsKey()
		{
			return "dbs";
		}

		private static string GetTablesKey(string db)
		{
			return db + ":tables";
		}

		private static string GetTableId(string db, string table)
		{
			return db + ":" + table + ":id";
		}

		private static string GetTableIndexsKey(string db, string table)
		{
			return db + ":" + table + ":indexs";
		}

		private static string GetTableIndex(this RedisClient client, string db, string table, List<string> index, CzfRow row)
		{
			string rowKey = GetRowKey (db, table, row["id"]);
			string keys = "";
			string vals = "";
			foreach (string column in index) {
				if (!row.ContainsKey (column))
					row [column]= client.GetValueFromHash (rowKey, column);
				keys += column + ":";
				vals += row[column] + ":";
			}
			vals = vals.TrimEnd (':');
			return db + ":" + table + ":index:" + keys + vals;
		}
		#endregion
	}
}

